﻿using System;
using System.IO;
using Assimp.Configs;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;
using Tesla.Scene;
using Tesla.Util;
using DevIL;

namespace Tesla.Content.Loaders {
    public class DevILTranslator {
        private ImageImporter m_importer;
        private ImageState m_imageState;
        private CompressionState m_compState;

        public DevILTranslator() {
            m_importer = new ImageImporter();
            m_imageState = new ImageState();
            m_compState = new CompressionState();
            m_compState.KeepDxtcData = true;
        }

        public Texture Load(IResource resource, ImageType imageType, ImageLoaderParameters loaderParams) {

            m_imageState.AbsoluteFormat = DataFormat.RGBA;
            m_imageState.AbsoluteDataType = DataType.UnsignedByte;
            m_imageState.AbsoluteOrigin = OriginLocation.UpperLeft;

            ApplyImageStates();

            using(Stream stream = resource.OpenStream()) {
               DevIL.Image image = m_importer.Load(stream, imageType);

               return CreateTexture(new ManagedImage(image));
            }
        }

        private Texture CreateTexture(ManagedImage image) {
            MipMapChainCollection mipMapChainCollection = image.Faces;
            int faceCount = mipMapChainCollection.Count;

            if(faceCount == 1) {
                MipMapChain face = mipMapChainCollection[0];
                if(face.Count > 0) {
                    ImageData mip0 = face[0];
                    int width = mip0.Width;
                    int height = mip0.Height;
                    int depth = mip0.Depth;

                    //Only create 1D/3D textures if we have width/depth greater than zero. So an image that is 1x1x1
                    //will become a texture2d
                    if(width > 1 && height == 1 && depth == 1) {
                        return CreateTexture1D(face);
                    } else if(depth > 1) {
                        return CreateTexture3D(face);
                    } else {
                        return CreateTexture2D(face);
                    }
                }
            } else if(faceCount > 1 && faceCount <= 6) {
                return CreateTextureCube(mipMapChainCollection);
            } 

            throw new ArgumentException("Invalid texture type");
        }

        private Texture1D CreateTexture1D(MipMapChain mipMapChain) {
            ImageData mip0 = mipMapChain[0];
            Texture1D tex = new Texture1D(mip0.Width, (mipMapChain.Count > 1), GetSurfaceFormat(mip0));

            for(int i = 0; i < mipMapChain.Count; i++) {
                ImageData mip = mipMapChain[i];
                byte[] data = (mip.HasCompressedData) ? mip.CompressedData : mip.Data;
                tex.SetData<byte>(data, i, 0, mip.Width, 0, data.Length);
            }
            return tex;
        }

        private Texture2D CreateTexture2D(MipMapChain mipMapChain) {
            ImageData mip0 = mipMapChain[0];
            Texture2D tex = new Texture2D(mip0.Width, mip0.Height, (mipMapChain.Count > 1), GetSurfaceFormat(mip0));

            for(int i = 0; i < mipMapChain.Count; i++) {
                ImageData mip = mipMapChain[i];
                byte[] data = (mip.HasCompressedData) ? mip.CompressedData : mip.Data;
                tex.SetData<byte>(data, i, null, 0, data.Length);
            }

            return tex;
        }

        private Texture3D CreateTexture3D(MipMapChain mipMapChain) {
            ImageData mip0 = mipMapChain[0];
            Texture3D tex = new Texture3D(mip0.Width, mip0.Height, mip0.Depth, (mipMapChain.Count > 1), GetSurfaceFormat(mip0));

            for(int i = 0; i < mipMapChain.Count; i++) {
                ImageData mip = mipMapChain[i];
                byte[] data = (mip.HasCompressedData) ? mip.CompressedData : mip.Data;
                tex.SetData<byte>(data, i, 0, mip.Width, 0, mip.Height, 0, mip.Depth, 0, data.Length);
            }

            return tex;
        }

        private TextureCube CreateTextureCube(MipMapChainCollection mipMapChainCollection) {
            ImageData mip0 = mipMapChainCollection[0][0];
            int cubeSize = mip0.Width;
            int mipCount = mipMapChainCollection[0].Count;
            TextureCube tex = new TextureCube(cubeSize, mipCount > 1, GetSurfaceFormat(mip0));

            foreach(MipMapChain mipChain in mipMapChainCollection) {
                mip0 = mipChain[0];

                //Not sure the order of our faces, so explicitly look at it and set by face. Not even sure if DevIL will give us all 6
                //if one is missing.
                EnvironmentMapFace face = mip0.CubeFace;
                bool unknownFace = false;
                CubeMapFace cubeFace = CubeMapFace.PositiveX;
                switch(face) {
                    case EnvironmentMapFace.PositiveX:
                        cubeFace = CubeMapFace.PositiveX;
                        break;
                    case EnvironmentMapFace.NegativeX:
                        cubeFace = CubeMapFace.NegativeX;
                        break;
                    case EnvironmentMapFace.PositiveY:
                        cubeFace = CubeMapFace.PositiveY;
                        break;
                    case EnvironmentMapFace.NegativeY:
                        cubeFace = CubeMapFace.NegativeY;
                        break;
                    case EnvironmentMapFace.PositiveZ:
                        cubeFace = CubeMapFace.PositiveZ;
                        break;
                    case EnvironmentMapFace.NegativeZ:
                        cubeFace = CubeMapFace.NegativeZ;
                        break;
                    default:
                        unknownFace = true;
                        break;
                }

                if(!unknownFace) {
                    for(int i = 0; i < mipChain.Count; i++) {
                        ImageData mip = mipChain[i];
                        byte[] data = (mip.HasCompressedData) ? mip.CompressedData : mip.Data;
                        tex.SetData<byte>(cubeFace, data, i, null, 0, data.Length);
                    }
                }
            }

            return tex;
        }

        private SurfaceFormat GetSurfaceFormat(ImageData image) {
            DataType dataType = image.DataType;
            DataFormat dataFormat = image.Format;
            CompressedDataFormat dxtcFormat = image.CompressedFormat;

            if(image.HasCompressedData) {
                switch(dxtcFormat) {
                    case CompressedDataFormat.DXT1:
                        return SurfaceFormat.DXT1;
                    case CompressedDataFormat.DXT3:
                        return SurfaceFormat.DXT3;
                    case CompressedDataFormat.DXT5:
                        return SurfaceFormat.DXT5;
                }
            }

            return SurfaceFormat.Color;
        }

        private void ApplyImageStates() {
            m_imageState.Apply();
            m_compState.Apply();
        }
    }
}
